package com.agen.basenet.util

import com.agen.basenet.download.DownloadCallback
import com.agen.basenet.download.DownloadState
import com.agen.basenet.service.DownloadService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.ResponseBody
import retrofit2.Retrofit
import java.io.File
import java.net.URL
import java.util.concurrent.TimeUnit

object DownloadManager {

    /**
     * 连接超时时间(秒)
     */
    private const val CONNECT_TIMEOUT = 5L

    /**
     * 读取数据超时时间(分钟)
     */
    private const val READ_TIMEOUT = 10L

    fun download(scope: CoroutineScope, url: String, file: File, callback: DownloadCallback) {
        scope.launch {
            download(url, file)
                .collect {
                    when (it) {
                        is DownloadState.Error -> {
                            callback.onError(it.throwable)
                        }
                        is DownloadState.Success -> {
                            callback.onSuccess(it.file.absolutePath)
                        }
                        is DownloadState.InProgress -> {
                            callback.onProgress(it.progress)
                        }
                    }
                }
        }
    }

    suspend fun download(url: String, file: File): Flow<DownloadState> {
        return flow {
            val retrofit = Retrofit.Builder()
                .baseUrl(URL(url).getBaseUrl())
                .client(getOkHttpClient())
                .build()
            val response = retrofit.create(DownloadService::class.java)
                .download(url)
                .execute()
            if (response.isSuccessful) {
                saveToFile(response.body()!!, file) {
                    emit(DownloadState.InProgress(it))
                }
                emit(DownloadState.Success(file))
            } else {
                emit(DownloadState.Error(java.io.IOException(response.toString())))
            }
        }.catch {
            emit(DownloadState.Error(it))
        }.flowOn(Dispatchers.IO)
    }

    private inline fun saveToFile(
        responseBody: ResponseBody,
        file: File,
        progressListener: (Int) -> Unit
    ) {
        val total = responseBody.contentLength()
        var bytesCopied = 0
        var emittedProgress = 0
        file.outputStream().use { output ->
            val input = responseBody.byteStream()
            val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
            var bytes = input.read(buffer)
            while (bytes >= 0) {
                output.write(buffer, 0, bytes)
                bytesCopied += bytes
                bytes = input.read(buffer)
                val progress = (bytesCopied * 100 / total).toInt()
                if (progress - emittedProgress > 0) {
                    progressListener(progress)
                    emittedProgress = progress
                }
            }
        }
    }

    private fun getOkHttpClient() = OkHttpClient
        .Builder()
        /**
         * 连接超时时间5秒
         */
        .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
        /**
         * 读取数据超时时间10分钟
         */
        .readTimeout(READ_TIMEOUT, TimeUnit.MINUTES)
        .build()
}
